﻿using System;
using System.Net;
using System.Net.Sockets;
using System.IO;
using Rites.Networking;

namespace Rites.GameObjects
{
    public delegate void UserEventDlg(object sender, ServerPlayer comms);
    public delegate void DataReceivedDlg(ServerPlayer sender, byte[] data);

    public class ServerPlayer : Player
    {
        //Networking internals
        private TcpClient client;
        private byte[] readBuffer = new byte[Constants.READBUFFERSIZE]; //Raw data buffer

        public string DataBuffer { get; set; }
        public bool Connected { get; set; }

        public long LastSendMessageID;

        //Events to call when the client received data or got disconnected
        public event UserEventDlg UserDisconnected;
        public event DataReceivedDlg DataReceived;

        public ServerPlayer(TcpClient client, byte id)
        {
            this.ID = id;
            this.client = client;
            this.client.NoDelay = true;

            this.StartListening();
        }

        private void StartListening()
        {
            this.client.GetStream().BeginRead(this.readBuffer, 0, Constants.READBUFFERSIZE, this.StreamReceived, null);
        }

        private void StreamReceived(IAsyncResult ar)
        {
            //Note: This method is not executed on the same thread as the one that called BeginRead
            //This means that you have to be careful when you modify data in the DataReceived-event
            int bytesRead = 0;
            try
            {
                //Lock the stream to prevent objects from other threads to acess it at the same time
                //Then call EndRead(ar) where ar is the IAsyncResult the TcpClient sent to us.
                //This will return the number of bytes that has been received
                lock (client.GetStream())
                    bytesRead = this.client.GetStream().EndRead(ar);
            }
            catch (Exception)
            { } //If we get an exception bytesread will remain 0 and we will disconnect.

            //If bytesRead is 0 we have lost connection to the server, save yourselves, it's too late for me
            if (bytesRead == 0)
            {
                this.Disconnect();
                return;
            }

            //Create a new buffer with just enough space to fit the received data
            byte[] data = new byte[bytesRead];

            //Copy the data from the readBuffer to data.
            //The reason why we do this instead of sending the entire readBuffer with the DataReceived-event is
            //that we want to start listening for new messages asap. But if we start listening before we call the
            //DataReceived-event and new data is received before the DataReceived-method has finished the 
            //readBuffer will change and the DataReceived-method will process completly corrupt data.
            for (int i = 0; i < bytesRead; i++)
                data[i] = readBuffer[i];

            //Start listening for new data
            this.StartListening();

            //Tell everyone that we've received data.
            if (this.DataReceived != null)
                this.DataReceived(this, data);
        }

        public void Disconnect()
        {
            if (this.Connected)
            {
                this.Connected = true;
                try
                {
                    if (this.client != null)
                    {
                        if (this.client.Connected)
                            this.client.GetStream().Close(500);
                        else
                            this.client.Close();
                    }
                }
                catch (ObjectDisposedException)
                {
                    this.client.Close();
                }

                if (this.UserDisconnected != null)
                    this.UserDisconnected(this, this);
            }
        }

        public void SendMemoryStream(System.IO.MemoryStream ms)
        {
            lock (ms)
            {
                int bytesWritten = (int)ms.Position;
                byte[] result = new byte[bytesWritten];

                ms.Position = 0;
                ms.Read(result, 0, bytesWritten);

                try
                {
                    //Lock the stream and send away data asyncrounously
                    //We can register a callback-method to be called when the data has been received like we did with 
                    //BeginRead but in this example there is no need to know that the data has
                    //been sent so we set it to null.
                    //
                    //BeginWrite can throw an exception when the client has been disconnected or when you
                    //pass a buffer with incorrect size, we only catch the ones that occur when you've got disconnected
                    lock (client.GetStream())
                        this.client.GetStream().BeginWrite(result, 0, result.Length, null, null);
                }
                catch (System.IO.IOException)
                {
                    this.Disconnect();
                }
                catch (InvalidOperationException)
                {
                    this.Disconnect();
                }
                catch (Exception e)
                {
                    throw e;
                }
            }
        }

        public void SendMessage(Rites.Networking.RitesProtocol messageType, string messageData)
        {
            System.IO.MemoryStream ms = Util.CreateMessage(messageType, messageData, out LastSendMessageID, 0);
            SendMemoryStream(ms);
        }
    }
}